<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="HelWv" id="HelWv"><span data-lake-id="u7208f2cd" id="u7208f2cd">典型回答</span></h1>
  <p data-lake-id="ud1ef7e2d" id="ud1ef7e2d"><span data-lake-id="u7df45dcd" id="u7df45dcd">在编程中，如果遇到并发安全的情况，有哪些方案可以来实现线程安全呢？以下是几个常见的方案（本文没有局限某种编程语言，而是说的整体思想）：</span></p>
  <p data-lake-id="uc952df7a" id="uc952df7a"><span data-lake-id="u13f26baa" id="u13f26baa">​</span><br></p>
  <p data-lake-id="ub3695874" id="ub3695874"><strong><span data-lake-id="u1af705be" id="u1af705be">1、单线程</span></strong></p>
  <p data-lake-id="u6d573f75" id="u6d573f75"><strong><span data-lake-id="u95a754bd" id="u95a754bd">​</span></strong><br></p>
  <p data-lake-id="uc3a23089" id="uc3a23089"><span data-lake-id="u714eb8b8" id="u714eb8b8">想要实现线程安全，最简单的方式就是干脆不支持多线程，只用单线程来执行，那么就可以从根本上杜绝线程安全的问题了。比如Redis，就是这种思想，在命令执行时，只依赖单线程进行。</span></p>
  <p data-lake-id="uaf3c5bd5" id="uaf3c5bd5"><span data-lake-id="u7bfb8d84" id="u7bfb8d84">​</span><br></p>
  <p data-lake-id="uc206a846" id="uc206a846"><span data-lake-id="ufc263525" id="ufc263525">​</span><br></p>
  <p data-lake-id="u2b0a780d" id="u2b0a780d"><strong><span data-lake-id="uefe32882" id="uefe32882">2、互斥锁</span></strong></p>
  <p data-lake-id="u098c1761" id="u098c1761"><span data-lake-id="u96f81c59" id="u96f81c59">​</span><br></p>
  <p data-lake-id="u48eec7f5" id="u48eec7f5"><span data-lake-id="ucd2b1ffa" id="ucd2b1ffa">如果一定要用多线程，比较有效的方式就是排队，那么加锁是一种比较常见的排队方式，无论是synchronized、reentrantLock这种单机锁，还是Redis实现的分布式锁，还是数据库中的乐观锁、悲观锁，本地思想都是通过加互斥锁的方式让多个并发请求排队执行。</span></p>
  <p data-lake-id="u6938b724" id="u6938b724"><span data-lake-id="u8f4af9b1" id="u8f4af9b1">​</span><br></p>
  <p data-lake-id="u7e7719a6" id="u7e7719a6"><span data-lake-id="ub4470640" id="ub4470640">​</span><br></p>
  <p data-lake-id="uc1c5af67" id="uc1c5af67"><strong><span data-lake-id="u2ca9e6ef" id="u2ca9e6ef">3、读写分离</span></strong></p>
  <p data-lake-id="u4192ebb3" id="u4192ebb3"><span data-lake-id="u48019084" id="u48019084">​</span><br></p>
  <p data-lake-id="ud8c3ceb5" id="ud8c3ceb5"><span data-lake-id="u4240b73f" id="u4240b73f">除了加锁以外，还有一种做法，那就是读写分离，比如Java并发包中有一种COW机制，即写时复制，主要就是读和写作分离的思想，因为读操作并发是没什么影响的，而写操作的话，只需要让他不发生并发就行了。</span></p>
  <p data-lake-id="u9d0cd0ac" id="u9d0cd0ac"><span data-lake-id="ud94ad7b6" id="ud94ad7b6">​</span><br></p>
  <p data-lake-id="u5902158c" id="u5902158c"><span data-lake-id="u3adbe3ce" id="u3adbe3ce">比如，CopyOnWriteArrayList使用了一种叫写时复制的方法，当有新元素add到CopyOnWriteArrayList时，先从原有的数组中拷贝一份出来，然后在新的数组做写操作，写完之后，再将原来的数组引用指向到新数组。</span></p>
  <p data-lake-id="u1ed403e1" id="u1ed403e1"><span data-lake-id="ua62e342f" id="ua62e342f">​</span><br></p>
  <p data-lake-id="u14bb9bae" id="u14bb9bae"><br></p>
  <p data-lake-id="uf6500751" id="uf6500751"><strong><span data-lake-id="ua3a09772" id="ua3a09772">4、原子操作</span></strong></p>
  <p data-lake-id="ub4ccb4ef" id="ub4ccb4ef"><span data-lake-id="uf2f10c3b" id="uf2f10c3b">​</span><br></p>
  <p data-lake-id="u4dfd3f85" id="u4dfd3f85"><span data-lake-id="u86b88e16" id="u86b88e16">原子操作是不可中断的操作，要么全部执行成功，要么全部失败。在多线程环境中，可以使用原子操作来实现对共享资源的安全访问，例如Java中的AtomicInteger等操作。</span></p>
  <p data-lake-id="u987bec28" id="u987bec28"><span data-lake-id="ue849d02e" id="ue849d02e">​</span><br></p>
  <p data-lake-id="u200c8b0d" id="u200c8b0d"><span data-lake-id="u3eec03a3" id="u3eec03a3">原子操作底层一般都是依赖的操作系统的CAS指令，思想也就是Compare And Swap</span></p>
  <p data-lake-id="u0d8f2bd0" id="u0d8f2bd0"><span data-lake-id="ud4014699" id="ud4014699">​</span><br></p>
  <p data-lake-id="ue49d6a2f" id="ue49d6a2f"><span data-lake-id="u63864c8f" id="u63864c8f">​</span><br></p>
  <p data-lake-id="uff9ba509" id="uff9ba509"><strong><span data-lake-id="u3d95728d" id="u3d95728d">5、不可变模式</span></strong></p>
  <p data-lake-id="ua78f85f7" id="ua78f85f7"><span data-lake-id="ue45b4fe1" id="ue45b4fe1">​</span><br></p>
  <p data-lake-id="u3cce49a1" id="u3cce49a1"><span data-lake-id="u0a75ffb7" id="u0a75ffb7">​</span><br></p>
  <p data-lake-id="uf92c3f3c" id="uf92c3f3c"><span data-lake-id="u0ba8f874" id="u0ba8f874">并发问题之所以发生，有个重要原因就是因为有共享变量，试想一下，如果只有读的情况，那么永远也不会出现线程安全的问题，因为多线程读永远是线程安全的，但是多线程读写一定会存在线程安全的问题。</span></p>
  <p data-lake-id="u707cb862" id="u707cb862"><br></p>
  <p data-lake-id="uf9538288" id="uf9538288"><span data-lake-id="u29060dc3" id="u29060dc3">那既然这么说是不是通过只读就能解决并发问题呢？其实最简单的办法就是让共享变量只有读操作，而没有写操作。这个办法如此重要，以至于被上升到了一种解决并发问题的设计模式：</span><strong><span data-lake-id="u9346e01c" id="u9346e01c">不变性（Immutability）模式</span></strong><span data-lake-id="udfeb3046" id="udfeb3046">。</span></p>
  <p data-lake-id="u4c5247db" id="u4c5247db"><span data-lake-id="uc84d7c88" id="uc84d7c88">​</span><br></p>
  <p data-lake-id="u268d166f" id="u268d166f"><br></p>
  <p data-lake-id="u225aa0cc" id="u225aa0cc"><span data-lake-id="u3aa6f527" id="u3aa6f527">如Java中的String就是不可变模式的一种体现，他的好处就是永远不会出现并发问题。</span></p>
  <p data-lake-id="u6f587e85" id="u6f587e85"><span data-lake-id="uef593721" id="uef593721">​</span><br></p>
  <p data-lake-id="ue12a8430" id="ue12a8430"><br></p>
  <p data-lake-id="u3435e0e1" id="u3435e0e1"><strong><span data-lake-id="uecc6049d" id="uecc6049d">6、数据不共享</span></strong></p>
  <p data-lake-id="u52142b3b" id="u52142b3b"><span data-lake-id="u4f8e3eaa" id="u4f8e3eaa">​</span><br></p>
  <p data-lake-id="u0407a4b4" id="u0407a4b4"><span data-lake-id="ub3d3ebb6" id="ub3d3ebb6">像前面说的，如果没有共享数据，那么就不会有线程安全问题了，除了不可变模式，还有一种我们常用的手段来避免并发问题。那就是用ThreadLocal</span></p>
  <p data-lake-id="u3c112f15" id="u3c112f15"><span data-lake-id="u50c59cd6" id="u50c59cd6">​</span><br></p>
 </body>
</html>